package cn.cloudtogo.server.mysql.service;

import cn.cloudtogo.common.Assert;
import cn.cloudtogo.common.GendbException;
import cn.cloudtogo.common.model.ExternalDatasourceModel;
import cn.cloudtogo.common.types.RequestHeaderObject;
import cn.cloudtogo.server.mysql.SpringJdbcTemplateContext;
import cn.cloudtogo.support.mysql.column.ColumnDefinition;
import cn.cloudtogo.support.mysql.column.Types;
import cn.cloudtogo.support.mysql.table.TableApi;
import cn.cloudtogo.support.mysql.table.TableBuilder;
import cn.cloudtogo.support.mysql.table.TableMetadata;
import cn.cloudtogo.support.mysql.table.TableTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.cloudtogo.common.types.RequestHeaderObject.extractExternalDatasourceModel;
import static cn.cloudtogo.common.util.StringHelper.pretty;

/**
 * @author yaolianhua789@gmail.com
 **/
@Component
@Slf4j
public class Tables implements TableApi {

    private final SpringJdbcTemplateContext context;

    public Tables(SpringJdbcTemplateContext context) {
        this.context = context;
    }

    @Override
    public void create(RequestHeaderObject requestHeaderObject, TableTemplate table) {
        JdbcTemplate jdbc = context.getJdbcTemplate(requestHeaderObject);
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);

        List<String> tables = tables(requestHeaderObject);
        Assert.argument(StringUtils.hasText(table.getName()), () -> "table name is null");
        if (tables.contains(table.getName())) {
            throw new GendbException(String.format("Table name '%s' already exist", table.getName()));
        }

        String sql = TableBuilder.buildSql(table, model.getSchema());
        log.debug(pretty(requestHeaderObject, "Create table", table.getName(), sql));
        jdbc.execute(sql);
        log.info("create table '{}.{}' success", model.getSchema(), table.getName());
    }

    @Override
    public void delete(RequestHeaderObject requestHeaderObject, String table) {
        JdbcTemplate jdbc = context.getJdbcTemplate(requestHeaderObject);
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);
        String sql = String.format("drop table if exists %s.%s", model.getSchema(), table);
        log.debug(pretty(requestHeaderObject, "Drop table", table, sql));
        jdbc.execute(sql);
        log.info("delete table '{}.{}' success", model.getSchema(), table);
    }

    @Override
    public void rename(RequestHeaderObject requestHeaderObject, String table, String newTableName) {
        Assert.argument(StringUtils.hasText(newTableName), () -> "new column name is null");
        JdbcTemplate jdbcTemplate = context.getJdbcTemplate(requestHeaderObject);
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);
        String sql = String.format("alter table %s.%s rename to %s.%s", model.getSchema(), table, model.getSchema(), newTableName);
        log.debug(pretty(requestHeaderObject, "Rename table", table, sql));
        jdbcTemplate.execute(sql);

    }

    @Override
    public List<String> tables(RequestHeaderObject requestHeaderObject) {

        JdbcTemplate jdbc = context.getJdbcTemplate(requestHeaderObject);
        List<String> tables = new LinkedList<>();
        log.debug(pretty(requestHeaderObject, "List tables name", null, null));
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);
        String schema = model.getSchema();
        try {
            ResultSet resultSet = JdbcUtils.extractDatabaseMetaData(Objects.requireNonNull(jdbc.getDataSource()),
                    e -> e.getTables(schema, null, null, new String[]{"TABLE"}));

            while (resultSet.next()) {
                tables.add(resultSet.getString(3));
            }

        } catch (MetaDataAccessException | SQLException e) {
            log.error("list '{}' tables name error '{}'", schema, e.getMessage(), e);
            Throwable cause = e.getCause();
            if (cause instanceof CannotGetJdbcConnectionException) {
                throw new CannotGetJdbcConnectionException("中间件连接异常");
            }
            throw new GendbException(String.format("list '%s' tables name error '%s'", schema, e.getMessage()));
        }
        return tables;

    }

    //TODO 查询会比较慢，先不要使用
    @Override
    public List<TableMetadata> tablesMetadata(RequestHeaderObject requestHeaderObject) {
        JdbcTemplate jdbc = context.getJdbcTemplate(requestHeaderObject);
        List<String> tables = new LinkedList<>();
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);
        String schema = model.getSchema();
        try {
            ResultSet resultSet = JdbcUtils.extractDatabaseMetaData(Objects.requireNonNull(jdbc.getDataSource()),
                    e -> e.getTables(schema, null, null, new String[]{"TABLE"}));

            while (resultSet.next()) {
                tables.add(resultSet.getString(3));
            }
            List<TableMetadata> tableMetadataList = tables.stream()
                    .map(name -> tableMetadata(requestHeaderObject, name))
                    .collect(Collectors.toList());
            return tableMetadataList;
        } catch (MetaDataAccessException | SQLException e) {
            log.error("get '{}' table metadata list error '{}'", schema, e.getMessage(), e);
            throw new GendbException(String.format("get '%s' table metadata list error '%s'", schema, e.getMessage()));
        }


    }

    @Override
    public TableMetadata tableMetadata(RequestHeaderObject requestHeaderObject, String table) {
        JdbcTemplate jdbcTemplate = context.getJdbcTemplate(requestHeaderObject);
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);
        String schema = model.getSchema();
        TableMetadata tableMetadata = new TableMetadata();
        tableMetadata.setSchema(schema);
        tableMetadata.setName(table);
        List<ColumnDefinition> columns = new LinkedList<>();
        log.debug(pretty(requestHeaderObject, "Get table metadata", table, null));
        try {
            ResultSet resultSet = JdbcUtils.extractDatabaseMetaData(Objects.requireNonNull(jdbcTemplate.getDataSource()),
                    e -> e.getColumns(schema, null, table, null));

            while (resultSet.next()) {
                ColumnDefinition definition = new ColumnDefinition();
                definition.setNotNull(!resultSet.getBoolean("IS_NULLABLE"));
                definition.setName(resultSet.getString("COLUMN_NAME"));
                definition.setAutoIncrement(resultSet.getBoolean("IS_AUTOINCREMENT"));
                definition.setComment(resultSet.getString("REMARKS"));

                Types.ColumnType columnType = Types.ColumnType.valueOf(resultSet.getString("TYPE_NAME"));
                Types types = Types.of(columnType);
                int size = resultSet.getInt("COLUMN_SIZE");
                types.setLength(String.valueOf(size));
                definition.setTypes(types);

                definition.setDefaultValue(resultSet.getString("COLUMN_DEF"));
                columns.add(definition);
            }

            ResultSet pkResultSet = JdbcUtils.extractDatabaseMetaData(Objects.requireNonNull(jdbcTemplate.getDataSource()),
                    e -> e.getPrimaryKeys(schema, null, table));
            while (pkResultSet.next()) {
                String pkName = pkResultSet.getString("COLUMN_NAME");
                columns.stream().filter(e -> Objects.equals(pkName, e.getName()))
                        .forEach(e -> e.setPrimaryKey(true));
            }

            tableMetadata.setColumns(columns);

        } catch (MetaDataAccessException | SQLException e) {
            log.error("get table metadata error '{}'", e.getMessage(), e);
            throw new GendbException("get table metadata error", e);
        }

        return tableMetadata;
    }
}
